package io.wongxd.demo.viewImg


import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.gestures.rememberTransformableState
import androidx.compose.foundation.gestures.transformable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.boundsInParent
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.lerp
import coil.compose.ImagePainter
import coil.compose.rememberImagePainter
import io.wongxd.demo.rememberPainter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.math.absoluteValue


@Composable
private fun rememberImgViewerState(): ImgViewerState {
    val scope = rememberCoroutineScope()
    return remember { ImgViewerState(scope) }
}

@Stable
private class ImgViewerState(private val scope: CoroutineScope) {
    val imgList = mutableStateListOf<Any>()
    val curPos = mutableStateOf(0)

    val srcRect = mutableStateOf(Rect.Zero)
    val srcOffset = mutableStateOf(Offset.Zero)

    val show = mutableStateOf(false)

    // 动画时长
    val animatorDuration = 432
    private val animator = androidx.compose.animation.core.Animatable(0f)
    val percent = mutableFloatStateOf(0f)
    val isAnimating = mutableStateOf(false)

    fun show(images: List<Any>, pos: Int, rect: Rect) {
        imgList.clear()
        imgList.addAll(images)
        curPos.value = pos
        srcRect.value = rect
        srcOffset.value = Offset(rect.left, rect.top)
        show.value = true
        scope.launch {
            isAnimating.value = true
            animator.animateTo(1f, animationSpec = tween(durationMillis = animatorDuration)) {
                percent.value = value
            }
            isAnimating.value = false
        }
    }

    fun hide() {
        scope.launch {
            isAnimating.value = true
            animator.animateTo(0f, animationSpec = tween(durationMillis = animatorDuration)) { percent.value = value }
            isAnimating.value = false
            show.value = false
        }
    }


}


@Composable
fun ImageWithViewer(imgList: List<Any>) {
    val ctx = LocalContext.current
    val dens = LocalDensity.current

    val state = rememberImgViewerState()

    LazyVerticalGrid(columns = GridCells.Fixed(3)) {
        items(imgList.size) { index ->
            val rect = remember { mutableStateOf(Rect.Zero) }
            val img = remember { imgList[index] }

            Image(
                painter = if (img is Int) painterResource(id = img) else rememberPainter(data = img as String),
                contentDescription = "",
                contentScale = ContentScale.FillWidth,
                modifier = Modifier
                    .onGloballyPositioned {
                        rect.value = it.boundsInParent()
                    }
                    .clickable {
                        if (state.show.value) return@clickable
                        state.show(imgList, index, rect = rect.value)
                    }
            )
        }
    }

    if (state.show.value) BigImage(imgList[state.curPos.value], state)

}

@Composable
private fun BigImage(img: Any, state: ImgViewerState) {
    val ctx = LocalContext.current
    val conf = LocalConfiguration.current
    val dens = LocalDensity.current

    val minW = remember { with(dens) { state.srcRect.value.size.toDpSize().width } }
    val maxW = remember { conf.screenWidthDp.dp }
    val maxWidthPx = remember { with(dens) { maxW.toPx() } }
    val minH = remember { with(dens) { state.srcRect.value.size.toDpSize().height } }
    val maxH = remember { conf.screenHeightDp.dp }

    val size = remember {
        derivedStateOf {
            val bigShow = state.show.value
            val per = state.percent.value
            DpSize(
                androidx.compose.ui.unit.lerp(if (bigShow) minW else maxW, if (bigShow) maxW else minW, per),
                androidx.compose.ui.unit.lerp(if (bigShow) minH else maxH, if (bigShow) maxH else minH, per)
            )
        }
    }

    val dragOffset = remember { mutableStateOf(Offset.Zero) }
    val dragFraction = remember { derivedStateOf { dragOffset.value.y.absoluteValue / maxWidthPx } }
    val maskAlpha = remember {
        derivedStateOf {
            val dragAlpha = lerp(1f, 0f, dragFraction.value)
            if (state.isAnimating.value) {
                if (dragAlpha != 0f) state.percent.value.coerceAtMost(dragAlpha) else state.percent.value
            } else {
                if (dragOffset.value.y > 0) dragAlpha else 1f
            }
        }
    }

    val offset = remember {
        derivedStateOf {
            val bigShow = state.show.value
            val targetOffset = dragOffset.value
            if (state.isAnimating.value) {
                val srcOffset = state.srcOffset.value
                val per = state.percent.value
                androidx.compose.ui.geometry.lerp(if (bigShow) srcOffset else targetOffset, if (bigShow) targetOffset else srcOffset, per)
            } else targetOffset
        }
    }

    val scale = remember {
        mutableStateOf(1f)
    }

    val transState = rememberTransformableState(onTransformation = { zoomChange, panChange, rotationChange ->
        val oldScale = scale.value
        scale.value = (zoomChange * oldScale).coerceIn(1f, 5f)

        dragOffset.value += panChange


    })

    Box(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0f, 0f, 0f, maskAlpha.value.coerceIn(0f, 1f)))
    ) {


        Image(
            painter = if (img is Int) painterResource(id = img) else rememberPainter(data = img as String),
            contentDescription = "",
            modifier = Modifier
                .size(size.value)
//                .transformable(transState)
                .graphicsLayer {
//                    this.scaleX = scale.value
//                    this.scaleY = scale.value
                    this.translationX = offset.value.x
                    this.translationY = offset.value.y
                }
                .pointerInput(null) {
                    detectTapGestures(
                        onTap = {
                            if (state.show.value.not()) return@detectTapGestures
                            state.hide()
                        },
                        onDoubleTap = {
                            dragOffset.value = Offset.Zero
                            scale.value = if (scale.value == 1f) 2f else 1f
                        })
                }
                .pointerInput(Unit) {
                    detectDragGestures(
                        onDragStart = {
                            dragOffset.value = Offset.Zero
                        },
                        onDragEnd = {
                            if (dragFraction.value <= .4f) dragOffset.value = Offset.Zero else state.hide()
                        },
                        onDrag = { change: PointerInputChange, dragAmount: Offset ->
                            dragOffset.value += dragAmount
                        }
                    )
                }
        )
    }
}
